package cn.xo68.boot.auth.client.shiro;

import cn.xo68.boot.auth.client.properties.AuthProviderProperties;
import cn.xo68.boot.auth.core.cookie.MapCookieTemplate;
import cn.xo68.boot.auth.core.domain.OAuth2AuthenticationToken;
import cn.xo68.boot.auth.core.domain.Oauth2Principal;
import cn.xo68.boot.auth.core.properties.OAuthResourceProperties;
import cn.xo68.boot.auth.core.web.Oauth2TokenDiscern;
import cn.xo68.boot.web.web.WebContext;
import cn.xo68.core.entity.JsonResponsEntity;
import cn.xo68.core.util.StringTools;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.subject.WebSubject;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * oauth2 认证过滤器
 * @author wuxie
 * @date 2018-8-5
 */
public class OAuth2AuthenticationFilter extends AuthenticatingFilter {

    private static  final Logger logger= LoggerFactory.getLogger(OAuth2AuthenticationFilter.class);

    private MapCookieTemplate browerDataCookieTemplate;

    private SimpleCookie accessTokenCookie;

    private AuthProviderProperties authProviderProperties;
    private OAuthResourceProperties oAuthResourceProperties;

    private WebContext webContext;


    public MapCookieTemplate getBrowerDataCookieTemplate() {
        return browerDataCookieTemplate;
    }

    public void setBrowerDataCookieTemplate(MapCookieTemplate browerDataCookieTemplate) {
        this.browerDataCookieTemplate = browerDataCookieTemplate;
    }

    public SimpleCookie getAccessTokenCookie() {
        return accessTokenCookie;
    }

    public void setAccessTokenCookie(SimpleCookie accessTokenCookie) {
        this.accessTokenCookie = accessTokenCookie;
    }

    public AuthProviderProperties getAuthProviderProperties() {
        return authProviderProperties;
    }

    public void setAuthProviderProperties(AuthProviderProperties authProviderProperties) {
        this.authProviderProperties = authProviderProperties;
    }

    public OAuthResourceProperties getoAuthResourceProperties() {
        return oAuthResourceProperties;
    }

    public void setoAuthResourceProperties(OAuthResourceProperties oAuthResourceProperties) {
        this.oAuthResourceProperties = oAuthResourceProperties;
    }

    public WebContext getWebContext() {
        return webContext;
    }

    public void setWebContext(WebContext webContext) {
        this.webContext = webContext;
    }

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        OAuth2AuthenticationToken oAuth2AuthenticationToken=new OAuth2AuthenticationToken();
        Oauth2Principal oauth2Principal=new Oauth2Principal();
        oAuth2AuthenticationToken.setPrincipal(oauth2Principal);

        try{

            Oauth2TokenDiscern oauth2TokenDiscern=new Oauth2TokenDiscern(httpRequest, true, true, true, this.getAccessTokenCookie());
            //令牌
            String accessToken =oauth2TokenDiscern.getAccessToken();
            //this.getAccessTokenCookie().readValue((HttpServletRequest) request, (HttpServletResponse) response);
            if(StringTools.isNotEmpty(accessToken)){

                oAuth2AuthenticationToken.setCredential(accessToken);
                oauth2Principal.setAccessToken(accessToken);

                return oAuth2AuthenticationToken;
            }

        }catch (OAuthProblemException e){
            //logger.warn("过滤器中获取令牌令牌异常", e);
            //onLoginFailure(new OAuth2AuthenticationToken(),new AuthenticationException(e), request, response);
            logger.debug("未发现认证信息");
        }
        return new OAuth2AuthenticationToken();
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

        if( RequestMethod.OPTIONS.name().equalsIgnoreCase(WebUtils.toHttp(request).getMethod()) ){
            return true;
        }

        String error = request.getParameter("error");
        String errorDescription = request.getParameter("error_description");
        //如果服务端返回了错误，也就是服务端里检查不通过进入if里返回的错误
        if(!StringUtils.isEmpty(error)) {
            WebUtils.issueRedirect(request, response, authProviderProperties.getUnauthorizedUrl() + "?error=" + error + "error_description=" + errorDescription);
            return false;
        }
        return executeLogin(request, response);
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = this.createToken(request, response);
        if (token == null || token.getPrincipal()==null || StringTools.isEmpty(token.getPrincipal().toString())) {
            return this.onLoginFailure(token,new AuthenticationException("未发现任何凭证"),request, response);
        } else {
            try {
                Subject subject =  new WebSubject.Builder(request, response).buildSubject();
                subject.login(token);
                ThreadContext.bind(subject);
                return this.onLoginSuccess(token, subject, request, response);
            } catch (AuthenticationException var5) {
                return this.onLoginFailure(token, var5, request, response);
            }
        }
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
                                     ServletResponse response) throws Exception {
        return true;
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
                                     ServletResponse response) {
        logger.debug(">> 身份认证失败了");
        try {
            HttpServletRequest httpServletRequest=(HttpServletRequest) request;
            HttpServletResponse httpServletResponse= (HttpServletResponse) response;

            boolean isjson=webContext.isResponseJson(httpServletRequest);
            if(isjson){
                //如果响应是json，抛出json结构异常信息
                MediaType mediaType=MediaType.APPLICATION_JSON_UTF8;

                HttpHeaders headers = webContext.buildHeaders(true, null);
                webContext.writeJsonResponse(httpServletResponse,
                        ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                                .headers(headers)
                                .cacheControl(CacheControl.noCache())
                                .contentType(mediaType)
                                .body(JsonResponsEntity.unAuthorized().setError(OAuthError.ResourceResponse.INVALID_TOKEN).build()));
            }else {
                //通过简化模式去登录
                Map<String,String> parameters=new HashMap<>();
                parameters.put(OAuth.OAUTH_RESPONSE_TYPE,"token");
                parameters.put(OAuth.OAUTH_CLIENT_ID,oAuthResourceProperties.getClientId());
                parameters.put(OAuth.OAUTH_CLIENT_SECRET,oAuthResourceProperties.getClientSecret());
                parameters.put(OAuth.OAUTH_REDIRECT_URI, httpServletRequest.getRequestURL().toString());
                WebUtils.issueRedirect(request,response,this.authProviderProperties.getRootUri()+this.authProviderProperties.getAuthorizationUri(),parameters);
            }

        } catch (Exception e) {
            logger.info("鉴权失败",e);
        }
        return false;
    }
}